#include "config.h"
#include "List.h"
#include "Socket.h"

#include "sock_frame.h"

sock_server_info* sock_server_init(SOCKET fd)
{
	sock_server_info* info = 0;
	if (fd == INVALID_SOCKET) 
		return 0;

	info = (sock_server_info*)calloc(1, sizeof(sock_server_info));
	if (!info)
		return 0;

	info->fd = fd;
	info->maxfd = fd;
	FD_SET(fd, &info->rset);
	
	info->sockList = List_create();
	
	return info;
}

void sock_server_free(sock_server_info* info)
{
	if (!info)
		return;
	List_free(info->sockList);
	free(info);
}

void sock_server_build_fdset(sock_server_info* trans)
{
    ListPosition pos = List_headPosition(trans->sockList);
    FD_ZERO(&trans->rset);
	FD_ZERO(&trans->wset);
    trans->maxfd = 0;
    while(pos)	{
        sock_client_session* session = (sock_client_session*) List_next(trans->sockList, &pos);
		if (session->fd == INVALID_SOCKET) {
		}
		else {
			FD_SET(session->fd, &trans->rset);
			trans->maxfd = MAX(trans->maxfd, session->fd);
		}
    }

	trans->maxfd = MAX(trans->maxfd, trans->fd);
	FD_SET(trans->fd, &trans->rset);
}

static void server_onAccept(sock_server_info* trans)
{
	struct sockaddr_in addr;
	socklen_t addrlen = sizeof(struct sockaddr_in);
	SOCKET client = INVALID_SOCKET;
	sock_client_session* sess = 0;

	//accept
	client = accept(trans->fd, (struct sockaddr*)&addr, &addrlen);
	if (client == INVALID_SOCKET) {
		return;
	}

	//build session
	sess = (sock_client_session*)calloc(1, sizeof(sock_client_session));
	if (!sess) {
		return;
	}
	sess->fd = client;
	sess->pos = List_addTail(trans->sockList, sess);

	//notify 
	if (trans->onAccept) {
		if (!trans->onAccept(sess)) {
			return;
		}
	}

	//update server info:rset, maxfd, sockCount
	FD_SET(client, &trans->rset);
	trans->maxfd = MAX(trans->maxfd, client);
	trans->sockCount++;

}

static void client_session_close(sock_server_info* trans, sock_client_session* sess)
{
	//notify
	if (trans->onClose)
		trans->onClose(sess);

	//close socket
	if (sess->fd != INVALID_SOCKET) {
		closesocket(sess->fd);
		sess->fd = INVALID_SOCKET;
	}

	//update server info: sockList, fdset, sockCount
	List_remove(trans->sockList, sess->pos);	
	sock_server_build_fdset(trans);
	trans->sockCount--;
}

static void server_onRecv(sock_server_info* trans, sock_client_session* sess)
{
	if (trans->onRecv)
		trans->onRecv(sess);
	if (sess->need_close) {
		client_session_close(trans, sess);
	}
}

static void server_onSend(sock_server_info* trans, sock_client_session* sess)
{
	if (trans->onSend)
		trans->onSend(sess);
	if (sess->need_close) {
		client_session_close(trans, sess);
	}
}


int sock_server_run(sock_server_info* trans)
{
    fd_set rs, ws;
    struct timeval timeout;
	int max_fd = 0;
    int res = 0;
	int ret = 0;

    trans->sockCount = 0;
	trans->isrun = TRUE;

    while(trans->isrun) {       
        timeout = trans->timeout;
        rs = trans->rset;
        ws = trans->wset;
        res = select(trans->maxfd+1, &rs, &ws, NULL, &timeout);
        if (-1 == res) {
            //error
            if (EINTR == errno)
                continue;

            ret = 1;
            break;
        }
        else if (0 == res) {
        }       
        else if(FD_ISSET(trans->fd, &rs)) {
            server_onAccept(trans);
            if(--res <= 0)
                continue;
        }
        else {
            ListPosition pos;
            sock_client_session* session = 0;
            pos = List_headPosition(trans->sockList);
            while(pos) {
                session = (sock_client_session*)List_next(trans->sockList, &pos);
				if (session->fd == INVALID_SOCKET)
					continue;
                if(FD_ISSET(session->fd, &rs)) {
                    server_onRecv(trans, session);
                    if(--res <= 0)
                        break;
                }
                if(FD_ISSET(session->fd, &ws)) {
                    server_onSend(trans, session);
                    if(--res <= 0)
                        break;
                }
            }
        }

    }

	return ret;
}
